#include "WellModel.h"
#include "EventWrapper.h"
#include "RemovePieceNetworkEvent.h"
#include "MovePieceNetworkEvent.h"
#include "UpdateScoreInternalEvent.h"

WellModel::WellModel(PlayersPtr players, unsigned int widthOfWell)
        : rows(new RowsModel(players, widthOfWell)), pieceGenerator(new PieceGeneratorModel(players, widthOfWell)),
                players(players)
{
    addChild(rows);
    addChild(pieceGenerator);
}

WellModel::~WellModel()
{
}

void WellModel::init()
{
    rows->init();

    PlayerIds playerIds = players->getPlayerIds();
    for (PlayerIds::iterator it = playerIds.begin(); it != playerIds.end(); ++it)
    {
        addPiece(*it);
    }
}

void WellModel::addPiece(int playerId)
{
    PieceModel* piece = pieceGenerator->getNextPieceFor(playerId);
    addChild(piece);
    pieces[playerId] = piece;
    moveAddedPieceUpIfOtherPlayerIsInTheWay(playerId);
}

void WellModel::moveAddedPieceUpIfOtherPlayerIsInTheWay(int playerId)
{
    PlayerIds ids = getCollidedPlayers(playerId);
    if (!ids.empty())
    {
        move(playerId, Position(0, -4));
    }
}

void WellModel::handleEvent(const Event& event)
{
    event.visitWellModel(this);
}

void WellModel::addBlocksFromPiece(int playerId)
{
    Positions blockPositions = pieces[playerId]->getBlockPositions();
    rows->addBlocksAt(blockPositions, playerId);
}

void WellModel::movePieceLeft(int playerId)
{
    move(playerId, Position(-1, 0));
}

void WellModel::movePieceRight(int playerId)
{
    move(playerId, Position(1, 0));
}

void WellModel::movePieceDown(int playerId)
{
    bool ok = move(playerId, Position(0, 1));
    if (!ok)
    {
        addBlocksFromPiece(playerId);
        rows->removeFullRows(playerId);
        replacePieceForPlayer(playerId);
    }
}

bool WellModel::move(int playerId, const Position& delta)
{
    PieceModel* piece = pieces[playerId];
    piece->move(delta);
    bool ok = pushOtherPiecesIfNeeded(playerId, delta);
    if (ok && okToMove(playerId))
    {
        piece->updateClients();
        return true;
    }
    piece->reset();
    return false;
}

bool WellModel::pushOtherPiecesIfNeeded(int playerId, const Position& delta)
{
    PlayerIds collidedPlayers = getCollidedPlayers(playerId);
    for (PlayerIds::iterator it = collidedPlayers.begin(); it != collidedPlayers.end(); ++it)
    {
        bool ok = move(*it, delta);
        if (!ok)
        {
            return false;
        }
    }
    return true;
}

void WellModel::movePieceAsLowAsPossible(int playerId)
{
    PieceModel* piece = pieces[playerId];
    int y = 0;
    do
    {
        y++;
        piece->move(Position(0, y));
        moveCollidingPlayersInstantDown(playerId);
    }
    while (okToMove(playerId));
    piece->move(Position(0, y - 1));
}

void WellModel::movePieceInstantDown(int playerId)
{
    movePieceAsLowAsPossible(playerId);
    addBlocksFromPiece(playerId);
    rows->removeFullRows(playerId);
    replacePieceForPlayer(playerId);
}

void WellModel::rotatePiece(int playerId)
{
    PieceModel* piece = pieces[playerId];
    piece->rotateCW();
    if (okToRotate(playerId))
    {
        piece->updateClients();
    }
    piece->reset();
}

void WellModel::movePiecesDown()
{
    PlayerIds playerList = players->getPlayerIds();
    for (PlayerIds::iterator it = playerList.begin(); it != playerList.end(); ++it)
    {
        movePieceDown(*it);
    }
}

bool WellModel::okToMove(int playerId)
{
    return getCollidedPlayers(playerId).empty() && okToMoveCheckingBlocks(playerId);
}

bool WellModel::okToMoveCheckingBlocks(int playerId)
{
    Positions blockPositions = pieces[playerId]->getBlockPositions();
    for (Positions::iterator it = blockPositions.begin(); it != blockPositions.end(); ++it)
    {
        if (!rows->hasEmptySpaceAt(*it))
        {
            return false;
        }
    }
    return true;
}

bool WellModel::okToRotate(int playerId)
{
    pieces[playerId]->rotateCW();
    int x[] = { 0, -1, 1, 2 };
    for (int i = 0; i < 4; ++i)
    {
        pieces[playerId]->move(Position(x[i], 0));
        if (okToMove(playerId))
        {
            return true;
        }
    }
    return false;
}

void WellModel::replacePieceForPlayer(int playerId)
{
    addChangeEvent(new RemovePieceNetworkEvent(pieces[playerId]->getId()));
    removeChild(pieces[playerId]->getId());
    pieces.erase(playerId);
    addPiece(playerId);
    EventWrapper::push(new UpdateScoreInternalEvent(playerId, UpdateScoreInternalEvent::PIECE_DOWN));
}

PlayerIds WellModel::getCollidedPlayers(int playerId)
{
    PlayerIds result;
    for (PieceModelsMap::iterator it = pieces.begin(); it != pieces.end(); ++it)
    {
        if (pieces[playerId]->collideWith(it->second))
        {
            result.push_back(it->first);
        }
    }
    return result;
}

void WellModel::moveCollidingPlayersInstantDown(int playerId)
{
    PlayerIds collidingPlayers = getCollidedPlayers(playerId);
    for (PlayerIds::iterator it = collidingPlayers.begin(); it != collidingPlayers.end(); ++it)
    {
        movePieceInstantDown(*it);
    }
}
